home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / Palettes / ScrollViewDeluxe / ScrollViewDeluxe.m < prev    next >
Text File  |  1993-11-01  |  48KB  |  1,146 lines

  1. // Format: 80 columns, tabs = 4 spaces
  2. #import "ScrollViewDeluxe.h"
  3. #import "Ruler.h"
  4.  
  5. /******************************************************************************    
  6.     ScrollViewDeluxe - by Jeff Martin
  7.     
  8.     This object is an enhanced subclass of ScrollView. It adds the following features:
  9.     
  10.     Support for a 'top view', a view at the top of the scroll view that scrolls horizontally with the document but remains static when the document is scrolled vertically. This is useful to add column headers, rulers, etc, to a document. The top view can be added from inside of IB by connecting the topView outlet.
  11.     
  12.     Support for a 'left view', a view to the left of the scroll view that scrolls vertically with the document, but remains static when the document is scrolled horizontally. This is useful for adding rulers, line numbers, etc, to a document. The left view can be added from inside of IB by connecting the leftView outlet.
  13.     
  14.     Automatic support for rulers. If a topView or leftView has not been set, a call to setShowTopView:YES, setShowLeftView:YES, showRulers: or toggleRulers: will instanciate a member of rulerClass (see setRulerClass:). Depending on which ruler is called, the scrollview will try to call setHorizontal or setVertical on the new rulerClass instance. The default class is Ruler (from Draw). A ruler that supports flipping and smooth zooming is forthcoming.
  15.     
  16.     Support for synchronizing other scroll views. Other ScrollViews can be made to scroll, size and zoom with respect to the docView by using the addSyncViews: methods. This is useful for ruler type views that need to exist outside of the scrollers. One synchronized view of each type can be added in IB by connecting a given ScrollView to the syncViews, horizSyncViews or vertSyncViews outlets. Be sure and connect to a ScrollView and not its content view (this can be tricky for ScrollViews without scrollers). Synchronized ScrollViews should not have scrollers of their own (but they can).
  17.  
  18.     Automatic support for page up/down & left/right buttons. Simply call the method setPageUpDownButtonsVisible:YES or setPageLeftRightButtonsVisible:YES and these are added automatically. They are removed temporarily if the scroller is too small(less than an inch), if there is nothing to be scrolled or if the scrolling area is less than 2 pages.
  19.     
  20.     Automatic support for zooming. Simply call the method setShowZoomButton:YES and a popup list containing zoom values will be added to the horizontal scroller. Zooming is implemented by calling zoomTo:(float)zoomX :(float)zoomY on the docView(and topView, leftView, syncViews, horizSyncViews and vertSyncViews). If the docView does not respond to this method or returns NO from this method, automatic zooming is performed by scaling the clipView by the zoom amount. The 'Set...' item in the zoom popup allows for arbitrary scaling. The 'Fit' menu item calculates the zoom value necessary to fit the docView entirely in the scrollview. The default zooming behavior tends to fail on documents that contain NXImages. Implement zoomTo:: in your custom view to explicitly scale NXImages (returning NO if you don't otherwise handle zoom).
  21.     
  22.     Support for adding arbitrary views to the vertical and horizontal scrollers. This is useful for adding little gadgets like a 'goto page' control inside of the horizontal scroller. The page up/down & left/right buttons as well as the zoom button use this facility. Page left/right is assumed to be the first in the horizScrollerViews list if it exists. The zoom button is next. Other views should be added at [horizScrollerViews count]. Page up/down is assumed to be first in the vertScrollerViews list. Scroller views are temporarily removed in reverse order if the scroller is not long enough to accommodate them.
  23.     
  24.     The ScrollViewDeluxe palette allows you to create a ScrollViewDeluxe by command clicking the 'Group in ScrollView' menu item. The code is a complete hack inside of the SVDInspector code (at the bottom) and introduces a bug into IB that you can no longer drag the default TextObject/ScrollView in (it just disappears).
  25.  
  26. Written by: Jeff Martin (jmartin@next.com)
  27. You may freely copy, distribute and reuse the code in this example.  
  28. Don't even talk to me about warranties.
  29. ******************************************************************************/
  30.  
  31. @implementation ScrollViewDeluxe
  32.  
  33. - initFrame:(const NXRect *)rect
  34. {
  35.     char supportNibPath[MAXPATHLEN];
  36.     NXSize defaultRulerSize = { 23, 23 };
  37.     NXRect bogusFrame = {0,0,0,0};
  38.     
  39.     [super initFrame:rect];
  40.     
  41.     // The following nib has the page up/down buttons & tiffs, zoom button and
  42.     //    the zoom panel.
  43.     [[NXBundle bundleForClass:[self class]] getPath:supportNibPath 
  44.         forResource:"ScrollViewDeluxe" ofType:"nib"];
  45.     [NXApp loadNibFile:supportNibPath owner:self withNames:NO 
  46.         fromZone:[self zone]];
  47.         
  48.     // Hack to work around IB crasher caused by "Group in ScrollView" hack.
  49.     if(rect) NXSetRect(&bogusFrame,0,0, NX_WIDTH(rect)-23, NX_HEIGHT(rect)-23);
  50.     [self setDocView:[[View alloc] initFrame:&bogusFrame]];
  51.  
  52.     // Set reasonable defaults
  53.     [self setHorizScrollerRequired:YES];
  54.     [self setVertScrollerRequired:YES];
  55.     [self setPageScroll:20];
  56.     [self setPageUpDownButtonsVisible:YES];
  57.     [self setPageLeftRightButtonsVisible:YES];
  58.     [self setZoomButtonVisible:YES];
  59.     [self setBorderType:NX_BEZEL];
  60.     [self setBackgroundColor:NX_COLORLTGRAY];
  61.     [self setRulerClass:[Ruler class]];
  62.     [self setRulerSize:defaultRulerSize];
  63.     [self showRulers:self];
  64.     
  65.     return self;
  66. }
  67.  
  68. /******************************************************************************    
  69.     - topView, - setTopView:view
  70.  
  71.     topView returns the current topView. If none exists, it allocates a view of ruler class, sets it to be the topView and returns it.
  72.     setTopView: places the given view inside the scrollview (inside a clip view, topClip) at the top of the scroll view at its origional hieght but at the width of the docView. It returns the oldTopView.
  73. ******************************************************************************/
  74. - topView
  75. {
  76.     // If there isn't a topView, alloc and set rulerClass instance as default
  77.     if(!topView) {
  78.         NXRect rulerFrame = {0,0,0,0};
  79.         rulerFrame.size = [self rulerSize];
  80.         NX_WIDTH(&rulerFrame) = [contentView frameWidth];
  81.         [self setTopView:[[[self rulerClass] alloc] initFrame:&rulerFrame]];
  82.         if([topView respondsTo:@selector(setHorizontal)])
  83.             [topView setHorizontal];
  84.  
  85.     }
  86.     return topView;
  87. }
  88. - setTopView:view
  89. {
  90.     id old = topView;
  91.  
  92.     // Set the new topView, its ruler height, and remove it from where it was
  93.     topView = view;
  94.     rulerSize.height = [view frameHeight];
  95.     [topView removeFromSuperview];
  96.  
  97.     // Install it if need be
  98.     if([self topViewVisible]) 
  99.         { [self setTopViewVisible:NO]; [self setTopViewVisible:YES]; }
  100.     return old;
  101. }
  102.  
  103. /******************************************************************************
  104.     - (BOOL)topViewVisible, - setTopViewVisible:(BOOL)flag
  105.     
  106.     topViewVisible returns whether or not the topView is visible.
  107.     setTopViewVisible: will install the current topView inside a clipView on top of the docView if set to YES and will remove the existing topView if set to NO. Retiles the views, but does not call display.Returns self.
  108. ******************************************************************************/
  109. - (BOOL)topViewVisible { return topViewVisible; }
  110. - setTopViewVisible:(BOOL)flag;
  111. {
  112.     // If the new state is equal to the old, return
  113.     if(flag == topViewVisible) return self;
  114.     
  115.     topViewVisible = flag;
  116.     
  117.     // If we are setting topView to visible add it to hierarchy
  118.     if(topViewVisible) {
  119.     
  120.         // Allocate clipView to scroll leftView, and build view hierarchy
  121.         topClip = [[[ClipView alloc] init] setAutosizing:NX_WIDTHSIZABLE];
  122.         [self addSubview:topClip];
  123.         [topClip setDocView:[self topView]];
  124.     }
  125.     else {
  126.         // Remove topView and topClip. Free topClip and set it to NULL.
  127.         [[self topView] removeFromSuperview];
  128.         [topClip removeFromSuperview];
  129.         [topClip free]; topClip = NULL;
  130.     }
  131.     
  132.     // Retile to fix layout of views and synchronize topView to docView
  133.     [self tile];
  134.     if(topViewVisible) [self synchronizeClipView:topClip 
  135.         withClipView:contentView horizontally:YES vertically:NO];
  136.     return self;
  137. }
  138.  
  139. /******************************************************************************
  140.     - showTopView:sender, - hideTopView:sender, - toggleTopView:sender
  141.  
  142.     These convenience methods simply wrap around setTopViewVisible: and can be set to be called from menus or controls inside of InterfaceBuilder.
  143. ******************************************************************************/
  144. - showTopView:sender
  145. { [self setTopViewVisible:YES]; [self display]; return self; }
  146. - hideTopView:sender
  147. { [self setTopViewVisible:NO]; [self display]; return self; }
  148. - toggleTopView:sender 
  149. { [self setTopViewVisible:!topViewVisible]; [self display]; return self; }
  150.  
  151. /******************************************************************************    
  152.     - leftView, - setLeftView:newLeftView
  153.  
  154.     leftView returns the current leftView. If none exists, it allocates a view of ruler class, sets it to be the leftView and returns it.
  155.     setLeftView: places the given view inside the scrollview (inside a clip view, leftClip) at the left of the scroll view at its origional width but at the height of the docView. It returns the oldLeftView.
  156. ******************************************************************************/
  157. - leftView
  158. {
  159.     // If there isn't a leftView, alloc and set rulerClass instance as default
  160.     if(!leftView) {
  161.         NXRect rulerFrame = {0,0,0,0};
  162.         rulerFrame.size = [self rulerSize];
  163.         NX_HEIGHT(&rulerFrame) = [contentView frameHeight];
  164.         [self setLeftView:[[[self rulerClass] alloc] initFrame:&rulerFrame]];
  165.         if([leftView respondsTo:@selector(setVertical)])
  166.             [leftView setVertical];
  167.     }
  168.     return leftView;
  169. }
  170. - setLeftView:view
  171. {
  172.     id old = leftView;
  173.  
  174.     // Set the new leftView, its ruler Width, and remove it from where it was
  175.     leftView = view;
  176.     rulerSize.width = [view frameWidth];
  177.     [leftView removeFromSuperview];
  178.     
  179.     // Install leftView if necessary
  180.     if([self leftViewVisible])
  181.         { [self setLeftViewVisible:NO]; [self setLeftViewVisible:YES]; }
  182.     return old;
  183. }
  184.  
  185.  
  186. /******************************************************************************
  187.     - (BOOL)leftViewVisible, - setLeftViewVisible:(BOOL)flag
  188.         
  189.     leftViewVisible returns whether or not the leftView is visible.
  190.     setLeftViewVisible: will install the current leftView inside a clipView on left of the docView if set to YES and will remove the existing leftView if set to NO. Retiles the views, but does not call display. Returns self.
  191. ******************************************************************************/
  192. - (BOOL)leftViewVisible { return leftViewVisible; }
  193. - setLeftViewVisible:(BOOL)flag;
  194. {
  195.     // If the new state is equal to the old, return
  196.     if(flag == leftViewVisible) return self;
  197.     
  198.     leftViewVisible = flag;
  199.     
  200.     // If we are setting leftView to visible add it to hierarchy
  201.     if(leftViewVisible) {
  202.     
  203.         // Allocate clipView to scroll leftView, and build view hierarchy
  204.         leftClip = [[[ClipView alloc] init] setAutosizing:NX_WIDTHSIZABLE];
  205.         [self addSubview:leftClip];
  206.         [leftClip setDocView:[self leftView]];
  207.     }
  208.     else {
  209.         // Remove leftView and leftClip. Free leftClip and set it to NULL.
  210.         [[self leftView] removeFromSuperview];
  211.         [leftClip removeFromSuperview];
  212.         [leftClip free]; leftClip = NULL;
  213.     }
  214.     
  215.     // Retile to fix layout of views and synchronize leftView to docView
  216.     [self tile];
  217.     if(leftViewVisible) [self synchronizeClipView:leftClip 
  218.         withClipView:contentView horizontally:NO vertically:YES];
  219.     return self;
  220. }
  221.  
  222. /******************************************************************************
  223.     - showLeftView:sender, - hideLeftView:sender, - toggleLeftView:sender
  224.  
  225.     These convenience methods simply wrap around setLeftViewVisible: and can be set to be called from menus or controls inside of InterfaceBuilder.
  226. ******************************************************************************/
  227. - showLeftView:sender
  228. { [self setLeftViewVisible:YES]; [self display]; return self; }
  229. - hideLeftView:sender
  230. { [self setLeftViewVisible:NO]; [self display]; return self; }
  231. - toggleLeftView:sender 
  232. { [self setLeftViewVisible:!leftViewVisible]; [self display]; return self; }
  233.  
  234. /******************************************************************************
  235.     - showRulers:sender, - hideRuler:sender, - toggleRulers:sender
  236.     
  237.     These are convenience methods for showing/hiding/toggling top and left views as a pair. They wrap around the setTopViewVisible and setLeftViewVisible. These methods can be set to be called from menus or controls inside of InterfaceBuilder. They all return self.
  238. ******************************************************************************/
  239. - showRulers:sender 
  240. {
  241.     [window disableDisplay];
  242.     [self setTopViewVisible:YES]; [self setLeftViewVisible:YES];
  243.     [window reenableDisplay]; [self display];
  244.     return self;
  245. }
  246. - hideRulers:sender
  247. {
  248.     [window disableDisplay];
  249.     [self setTopViewVisible:NO]; [self setLeftViewVisible:NO];
  250.     [window reenableDisplay]; [self display];
  251.     return self;
  252. }
  253. - toggleRulers:sender
  254. {
  255.     [window disableDisplay];
  256.     
  257.     // If visible set to not visible and visa-versa.
  258.     [self setTopViewVisible:![self topViewVisible]];
  259.     [self setLeftViewVisible:![self leftViewVisible]];
  260.  
  261.     [window reenableDisplay];
  262.     [self display];
  263.     return self;
  264. }
  265.  
  266. /******************************************************************************
  267.     - (Class)rulerClass, - setRulerClass:(Class)class
  268.     
  269.     If the ScrollView deluxe is asked to show top or left views when none has been set, it attempts to allocate an instance of 'rulerClass' (assumed to be a view). If the instance responds to setHorizontal or setVertical, this will be called.
  270. ******************************************************************************/
  271. - (Class)rulerClass { return rulerClass; }
  272. - setRulerClass:(Class)class { rulerClass = class; return self; }
  273.  
  274. /******************************************************************************
  275.     - (NXSize)rulerSize, - setRulerSize:(NXSize)size
  276.     
  277.     When ScrollViewDeluxe allocates a default top/left view, it sets the top one to be of height rulerSize.height and the left one to be of width rulerSize.width. If a topView or leftView are added programatically, the rulerSize.height and rulerSize.width are set respectively.
  278. ******************************************************************************/
  279. - (NXSize)rulerSize { return rulerSize; }
  280. - setRulerSize:(NXSize)size { rulerSize = size; return self; }
  281.  
  282. /******************************************************************************
  283.     - syncView, - addSyncView:at:, - removeSyncView:, - removeSyncViewAt:
  284.     - horizSyncViews, - addHorizSyncView:at:, - removeHorizSyncView: & ViewAt:
  285.     - vertSyncViews, - addVertSyncView:at:, - removeVertSyncView: & ViewAt:
  286.         
  287.     syncViews are ScrollViews that are to be scrolled, sized and (optionally)zoomed with respect to the docViews position, size and zoom. horizSyncViews are only affected in the horizontal direction, while vertSyncViews are only affected in the vertical direction. Group a view inside of a ScrollView, disable its horizontal and vertical scrollers, and use one of the addMethods. In IB you can actually set one of each type view by setting the syncViews, horizSyncViews or vertSyncViews outlet to a ScrollView. It will be added to the list when the outlets are set.
  288.     The syncViews, horizSyncViews and vertSyncViews methods return the list of the views that are currently being syncronized in the respective direction(can be NULL if there are none).
  289.     addSyncView:at:, addHorizSyncView:at: and addVertSyncView:at: add scrollviews to be synchronized in the respective direction at the given location in the list(use [[mySVD syncViews] count] to add to end). Returns self.
  290.     removeSyncView:, removeHorizSyncView:, removeVertSyncView: remove the given view from its respective list by calling removeSyncAt: with the view's index.
  291.     removeSyncViewAt:, removeHorizSyncViewAt: and removeVertSyncViewAt: remove ScrollViews from the respective list of syncronized ScrollViews. Returns self.
  292. ******************************************************************************/
  293. - syncViews { return syncViews; }
  294. - addSyncView:view at:(int)at
  295. {
  296.     // Only add ScrollViews
  297.     if([view isKindOfClassNamed:"ScrollView"]) {
  298.         if(!syncViews) syncViews = [[List alloc] init];
  299.         [syncViews addObjectIfAbsent:view];
  300.     }
  301.     return self;
  302. }
  303. - removeSyncView:view
  304. { return [self removeSyncViewAt:[[self syncViews] indexOf:view]]; }
  305. - removeSyncViewAt:(int)at { [syncViews removeObjectAt:at]; return self; }
  306.  
  307. - horizSyncViews { return horizSyncViews; }
  308. - addHorizSyncView:view at:(int)at
  309. {
  310.     // Only add ScrollViews
  311.     if([view isKindOfClassNamed:"ScrollView"]) {
  312.         if(!horizSyncViews) horizSyncViews = [[List alloc] init];
  313.         [horizSyncViews addObjectIfAbsent:view];
  314.     }
  315.     return self;
  316. }
  317. - removeHorizSyncView:view
  318. { return [self removeHorizSyncViewAt:[[self horizSyncViews] indexOf:view]]; }
  319. - removeHorizSyncViewAt:(int)at { [horizSyncViews removeObjectAt:at]; return self; }
  320.  
  321. - vertSyncViews { return vertSyncViews; }
  322. - addVertSyncView:view at:(int)at
  323. {
  324.     // Only add ScrollViews
  325.     if([view isKindOfClassNamed:"ScrollView"]) {
  326.         if(!vertSyncViews) vertSyncViews = [[List alloc] init];
  327.         [vertSyncViews addObjectIfAbsent:view];
  328.     }
  329.     return self;
  330. }
  331. - removeVertSyncView:view
  332. { return [self removeVertSyncViewAt:[[self vertSyncViews] indexOf:view]]; }
  333. - removeVertSyncViewAt:(int)at { [vertSyncViews removeObjectAt:at]; return self; }
  334.  
  335. /******************************************************************************
  336.     - horizScrollerViews, - addHorizScrollerView:at:, -removeHorizScrollerView:
  337.     - vertScrollerViews, - addVertScrollerView:at:, - removeVertScrollerView:
  338.         
  339.     ScrollerViews are views embedded inside of the vertical or horizontal scrollers. The are frequently simple controls like a "Goto Page:" control. In fact the page up/down & left/right buttons as well as the zoomButton are horizScrollerViews (assumed to be at 0 and 1 respectively if they exist).When added these views are sized to fit into the scroller(ie, horizontal scroller views are constrained to the horizontal scroller's height).
  340.     The horizScrollerViews and vertScrollerViews methods return the list of the views that are currently in the respective scroller (can be NULL if there are none).
  341.     addHorizScrollerView:at: and addVertScrollerView:at: add a view to their respective list at the given location(use [[mySVD vertSyncViews] count] to add to end). They returns self.
  342.     removeHorizScrollerView: and removeVertScrollerView: removes the given view from the respective scroller list. Returns self.
  343.     removeHorizScrollerViewAt: and removeVertScrollerViewAt: removes the view at the given location from the respective scroller list. Returns self.
  344. ******************************************************************************/
  345. - horizScrollerViews { return horizScrollerViews; }
  346. - addHorizScrollerView:view at:(int)at
  347. {
  348.     // Make sure the list exists and add the view
  349.     if(!horizScrollerViews) horizScrollerViews = [[List alloc] init];
  350.     [horizScrollerViews insertObject:view at:at];
  351.  
  352.     // Add the view to subview list and retile
  353.     [self addSubview:view]; [self tile];
  354.     return self;
  355. }
  356. - removeHorizScrollerView:view
  357. {
  358.     [self removeHorizScrollerViewAt:[[self horizScrollerViews] indexOf:view]];
  359.     return self;
  360. }
  361. - removeHorizScrollerViewAt:(int)at
  362. {
  363.     [[horizScrollerViews removeObjectAt:at] removeFromSuperview];
  364.     [self tile];
  365.     return self;
  366. }
  367.  
  368. - vertScrollerViews { return vertScrollerViews; }
  369. - addVertScrollerView:view at:(int)at
  370. {
  371.     // Make sure the list exists and add the view.
  372.     if(!vertScrollerViews) vertScrollerViews = [[List alloc] init];
  373.     [vertScrollerViews insertObject:view at:at];
  374.  
  375.     // Add the view to subview list and retile
  376.     [self addSubview:view]; [self tile];
  377.     return self;
  378. }
  379. - removeVertScrollerView:view
  380. {
  381.     [self removeVertScrollerViewAt:[[self vertScrollerViews] indexOf:view]];
  382.     return self;
  383. }
  384. - removeVertScrollerViewAt:(int)at
  385. {
  386.     [[vertScrollerViews removeObjectAt:at] removeFromSuperview];
  387.     [self tile];
  388.     return self;
  389. }
  390.  
  391. /******************************************************************************
  392.     - pageUpDownButtons, - pageLeftRightButtons, - zoomButton
  393.     
  394.     These methods return pointers to the matrices that contain the up/down & left/right buttons. They are loaded in at init time from ScrollViewDeluxe.nib.
  395. ******************************************************************************/ 
  396. - pageUpDownButtons { return pageUpDownButtons; }
  397. - pageLeftRightButtons { return pageLeftRightButtons; }
  398. - zoomButton { return zoomButton; }
  399.  
  400. /******************************************************************************
  401.     - (BOOL)pageUpDownButtonsVisible, setPageUpDownButtonsVisible:(BOOL)flag 
  402.     - (BOOL)needUpDownButtons
  403.     - (BOOL)pageLeftRightButtonsVisible, setPageLeftRightButtonsVisible:(BOOL)f
  404.     - (BOOL)needPageLeftRightButtons
  405.     
  406.     These methods query and set whether the respective button set is visible.
  407.     The setButtonsVisible method calls the respective add or remove scrollerView method with the 'at' value equal to zero.
  408.     The needPageButtons methods return whether the page buttons are actually needed (ie, if the docView is smaller than the contentView or the scrollable area is less than 2 pages, the buttons are not needed).
  409. ******************************************************************************/ 
  410. - (BOOL)pageUpDownButtonsVisible { return pageUpDownButtonsVisible; }
  411. - setPageUpDownButtonsVisible:(BOOL)flag
  412. {
  413.     // If this is a new state then either add or remove buttons from scroller
  414.     if(pageUpDownButtonsVisible != flag) {
  415.         pageUpDownButtonsVisible = flag;
  416.         if(flag) [self addVertScrollerView:[self pageUpDownButtons] at:0];
  417.         else [self removeVertScrollerView:[self pageUpDownButtons]];
  418.     }
  419.     return self;
  420. }
  421. - (BOOL)needPageUpDownButtons { return (([vScroller perCent] < .5) && 
  422.     ([contentView boundsHeight] < [[contentView docView] frameHeight])); }
  423.  
  424. - (BOOL)pageLeftRightButtonsVisible { return pageLeftRightButtonsVisible; }
  425. - setPageLeftRightButtonsVisible:(BOOL)flag
  426. {
  427.     // If this is a new state then either add or remove buttons from scroller
  428.     if(pageLeftRightButtonsVisible != flag) {
  429.         pageLeftRightButtonsVisible = flag;
  430.         if(flag) [self addHorizScrollerView:[self pageLeftRightButtons] at:0];
  431.         else [self removeHorizScrollerView:[self pageLeftRightButtons]];
  432.     }
  433.     return self;
  434. }
  435. - (BOOL)needPageLeftRightButtons { return (([hScroller perCent] < .5) && 
  436.     ([contentView boundsWidth] < [[contentView docView] frameWidth])); }
  437.  
  438. /******************************************************************************
  439.     - pageButton
  440.  
  441.     This method is the target to all of the page up/down/left/right buttons. Based on the tags(pageUp=0, pageDown=1, pageLeft=2, pageRight=3), it scrolls the visible rect by its extents minus the page overlap (pageContext) in the respective direction. Returns self.
  442. ******************************************************************************/ 
  443. - pageButton:sender
  444. {
  445.     NXRect rect;
  446.     int flipped;
  447.     NXSize pageContextSize = {pageContext, pageContext};
  448.     NXCoord newPageCont;
  449.     
  450.     // Get the docView's visible rect in docView's coords.
  451.     [[self docView] getVisibleRect:&rect];
  452.     [[self docView] convertRectFromSuperview:&rect];
  453.  
  454.     // Convert the ScrollViews pageContext to docView(to correct for zooming)
  455.     [[self docView] convertSize:&pageContextSize fromView:self];
  456.     newPageCont = pageContextSize.width;
  457.     
  458.     flipped = [[self docView] isFlipped] ? -1 : 1;
  459.  
  460.     // Move the visible rect in the respective direction (up/down/left/right
  461.     //    respectively). Allow for flippedness and page overlap.
  462.     switch([sender selectedTag]) {
  463.         case 0: NX_Y(&rect) += (NX_HEIGHT(&rect) - newPageCont)*flipped; break;
  464.         case 1: NX_Y(&rect) -= (NX_HEIGHT(&rect) - newPageCont)*flipped; break;
  465.         case 2: NX_X(&rect) -= NX_WIDTH(&rect) - newPageCont; break;
  466.         case 3: NX_X(&rect) += NX_WIDTH(&rect) - newPageCont; break;
  467.     }
  468.     
  469.     // Scroll the new rect to visible.
  470.     [[self docView] scrollRectToVisible:&rect];
  471.     return self;
  472. }
  473.  
  474. /******************************************************************************
  475.     - (BOOL)zoomButtonVisible
  476.     - setZoomButtonVisible:(BOOL)flag
  477.  
  478.     These methods query and set whether the zoom button is visible.
  479.     setZoomButtonVisible: either adds the zoomButton to the vert scroller via - addHorizScrollerView: or removes via removeHorizScrollerView. Returns self.
  480. ******************************************************************************/
  481. - (BOOL)zoomButtonVisible { return zoomButtonVisible; }
  482. - setZoomButtonVisible:(BOOL)flag
  483. {
  484.     // If this is a new state then either add or remove button from scroller
  485.     if(zoomButtonVisible != flag) {
  486.         zoomButtonVisible = flag;
  487.         if(zoomButtonVisible) {
  488.             if(pageLeftRightButtonsVisible)
  489.                 [self addHorizScrollerView:[self zoomButton] at:1];
  490.             else [self addHorizScrollerView:[self zoomButton] at:0];
  491.         }
  492.         else [self removeHorizScrollerView:[self zoomButton]];
  493.     }
  494.     return self;
  495. }
  496.  
  497. /******************************************************************************
  498.     - zoom:sender
  499.  
  500.     This method is called by the zoomButton's popUpList to get the zoom amount. It reads the title and converts it to a scale (special cased for 'Set...' and 'Size To Fit'). Calls zoomTo::.
  501. ******************************************************************************/
  502. #define CLAMP(a,x,y) (MAX((x), MIN((y), (a))))
  503. - zoom:sender
  504. {
  505.     float scaleX, scaleY;
  506.     char *title;
  507.     
  508.     // Get the title of the selected menu item
  509.     title = NXCopyStringBuffer([[sender selectedCell] title]);
  510.  
  511.     // If it is "Fit" (tag == 6) the simply sizeTo:: to get size to fit...
  512.     if([sender selectedTag] == 6) {
  513.         NXRect docViewBounds, contentFrame;
  514.         [[self docView] getBounds:&docViewBounds];
  515.         [contentView getFrame:&contentFrame];
  516.         // Zoom so that docView's bounds == content's frame
  517.         scaleX = NX_WIDTH(&contentFrame)/(NX_WIDTH(&docViewBounds)+1);
  518.         scaleY = NX_HEIGHT(&contentFrame)/(NX_HEIGHT(&docViewBounds)+1);
  519.     }
  520.  
  521.     // Otherwise if it is "Set..." (tag == 7) then run the panel...
  522.     else if([sender selectedTag] == 7) {
  523.             static char string[32];
  524.             
  525.             [[zoomButton target] removeItem:string];
  526.             [zoomText selectText:self];
  527.             [NXApp runModalFor:zoomPanel];
  528.             [zoomPanel close];
  529.             scaleX = CLAMP([zoomText intValue],10,1600);
  530.             sprintf(string, "%d%%", (int)scaleX);
  531.             scaleX = scaleY = scaleX/100.0;
  532.             
  533.             // Add the value to the button (lazily since there is a problem
  534.             //    doing it interactively, and reset the popUp's action to zoom.
  535.             [zoomButton perform:@selector(setTitle:) with:(id)string 
  536.                 afterDelay:0 cancelPrevious:YES];
  537.             [[[zoomButton target] setTarget:self] setAction:@selector(zoom:)];
  538.     }
  539.     
  540.     // Otherwise convert the title from percentage to scale value
  541.     else {
  542.         title[strlen(title)-1] = '\0';
  543.         scaleX = scaleY = atoi(title)/100.0;
  544.     }
  545.     free(title);
  546.  
  547.     [self zoomTo:scaleX :scaleY];
  548.     return self;
  549. }
  550.  
  551. // This method is used to stop the zoomPanels modal state.
  552. - stopZoomPanel:sender { [NXApp stopModal]; return self; }
  553.  
  554. /******************************************************************************
  555.     - zoomTo:(float)zoomX :(float)zoomY
  556.  
  557.     This method tries to call zoomTo:: on the docView and all of the accessory views (topView, leftView, syncViews, horizSyncViews, vertSyncViews) with the given scale (1 is full size). If the views implement zoomTo:: and actually do the zoom, they should return YES. If they just implement zoomTo:: to get notification of a zoom or to scale dependent pieces(like NXImages), they should return NO. If zoomTo:: is not implemented or returns NO, automatic scaling takes place(on the ClipView).
  558. ******************************************************************************/
  559. - (BOOL)zoomTo:(float)scaleX :(float)scaleY
  560. {
  561.     NXRect docViewVis, docViewVis2;
  562.     int i,j;
  563.  
  564.     // Disable display so we don't see scales and scrolls
  565.     [window disableDisplay];
  566.  
  567.     // Get the current visible docView rect(to preserve current view)
  568.     [[self docView] getVisibleRect:&docViewVis];
  569.     [[self docView] convertRectFromSuperview:&docViewVis];
  570.     
  571.     // Zoom the docView
  572.     if(!([[contentView docView] respondsTo:@selector(zoomTo::)] &&
  573.         [[contentView docView] zoomTo:scaleX :scaleY])) {
  574.         [contentView setDrawSize:[contentView frameWidth] 
  575.             :[contentView frameHeight]];
  576.         [contentView scale:scaleX :scaleY];
  577.         [self reflectScroll:contentView];
  578.     }
  579.  
  580.     // Zoom the topView
  581.     if(!([topView respondsTo:@selector(zoomTo::)] &&
  582.         [topView zoomTo:scaleX :scaleY])) {
  583.         [topClip setDrawSize:[topClip frameWidth] :[topClip frameHeight]];
  584.         [topClip scale:scaleX :1];
  585.     }
  586.     
  587.     // Zoom the leftView
  588.     if(!([leftView respondsTo:@selector(zoomTo::)] &&
  589.         [leftView zoomTo:scaleX :scaleY])) {
  590.         [leftClip setDrawSize:[leftClip frameWidth] :[leftClip frameHeight]];
  591.         [leftClip scale:1 :scaleY];
  592.     }
  593.     
  594.     // Zoom the synchronized Views.
  595.     for(i=0; i<[syncViews count]; i++) {
  596.         id subviewList = [[syncViews objectAt:i] subviews];
  597.         
  598.         // Look inside each scrollview for its clipviews
  599.         for(j=0; j<[subviewList count]; j++) {
  600.             id view = [subviewList objectAt:j];
  601.             
  602.             // Only zoom the ClipViews of the scrollViews
  603.             if([view isKindOf:[ClipView class]]) {
  604.             
  605.                 // If view responds NO to zoomTo: scale the ClipView
  606.                 if(!([[view docView] respondsTo:@selector(zoomTo::)] &&
  607.                     [[view docView] zoomTo:scaleX :scaleY])) {
  608.                     [view setDrawSize:[view frameWidth] :[view frameHeight]];
  609.                     [view scale:scaleX :scaleY];
  610.                     
  611.                     // Only reflect Scroll the docView
  612.                     if([view docView] == [[view superview] docView])
  613.                         [[view superview] reflectScroll:view];
  614.                 }
  615.             }
  616.         }
  617.         [[horizSyncViews objectAt:i] display];
  618.     }
  619.     
  620.     // Zoom the horizontally synchronized Views.
  621.     for(i=0; i<[horizSyncViews count]; i++) {
  622.         id subviewList = [[horizSyncViews objectAt:i] subviews];
  623.         
  624.         // Look inside each scrollview for its clipviews
  625.         for(j=0; j<[subviewList count]; j++) {
  626.             id view = [subviewList objectAt:j];
  627.             
  628.             // Only zoom the ClipViews of the scrollViews
  629.             if([view isKindOf:[ClipView class]]) {
  630.             
  631.                 // If view responds NO to zoomTo: scale the ClipView
  632.                 if(!([[view docView] respondsTo:@selector(zoomTo::)] &&
  633.                     [[view docView] zoomTo:scaleX :1])) {
  634.                     [view setDrawSize:[view frameWidth] :[view frameHeight]];
  635.                     [view scale:scaleX :1];
  636.                     
  637.                     // Only reflect Scroll the docView
  638.                     if([view docView] == [[view superview] docView])
  639.                         [[view superview] reflectScroll:view];
  640.                 }
  641.             }
  642.         }
  643.         [[horizSyncViews objectAt:i] display];
  644.     }
  645.     
  646.     // Zoom the vertically synchronized views.
  647.     for(i=0; i<[vertSyncViews count]; i++) {
  648.         id subviewList = [[vertSyncViews objectAt:i] subviews];
  649.         
  650.         // Look inside each scrollview for its clipviews
  651.         for(j=0; j<[subviewList count]; j++) {
  652.             id view = [subviewList objectAt:j];
  653.             
  654.             // Only zoom the ClipViews of the scrollViews
  655.             if([view isKindOf:[ClipView class]]) {
  656.             
  657.                 // If view responds NO to zoomTo: scale the ClipView
  658.                 if(!([[view docView] respondsTo:@selector(zoomTo::)] &&
  659.                     [[view docView] zoomTo:1 :scaleY])) {
  660.                     [view setDrawSize:[view frameWidth] :[view frameHeight]];
  661.                     [view scale:1 :scaleY];
  662.                     
  663.                     // Only reflect Scroll the docView
  664.                     if([view docView] == [[view superview] docView])
  665.                         [[view superview] reflectScroll:view];
  666.                 }
  667.             }
  668.         }
  669.         [[vertSyncViews objectAt:i] display];
  670.     }
  671.     
  672.     // Get new visible size and inset old visible rect by the difference
  673.     [[self docView] getVisibleRect:&docViewVis2];
  674.     [[self docView] convertRectFromSuperview:&docViewVis2];
  675.     NXInsetRect(&docViewVis, 
  676.         (NX_WIDTH(&docViewVis) - NX_WIDTH(&docViewVis2))/2, 
  677.         (NX_HEIGHT(&docViewVis) - NX_HEIGHT(&docViewVis2))/2);
  678.         
  679.     // Scroll to new rect (as much to the center of the old rect as possible)
  680.     [[self docView] scrollRectToVisible:&docViewVis];
  681.     
  682.     // Renable display and display final change
  683.     [window reenableDisplay]; [window display];
  684.  
  685.     return YES;
  686. }    
  687.  
  688. /******************************************************************************
  689.     - scrollClip:clipView to:(const NXPoint *)point
  690.  
  691.     This method is called automatically whenever any of the clipView subviews are scrolled. We intercept it so that we can perform the scroll on all of the dependant views(topView, leftView, syncViews, horizSyncViews & vertSyncViews). Returns Self.
  692. ******************************************************************************/
  693. - scrollClip:(ClipView *)clipView to:(const NXPoint *)aPoint
  694. {
  695.     int i,j;
  696.     
  697.     // RawScroll the given clipView to given point
  698.     [clipView rawScroll:aPoint];
  699.  
  700.     // Scroll the docView
  701.     if(contentView != clipView)
  702.         [self synchronizeClipView:contentView withClipView:clipView
  703.             horizontally:(clipView==topClip) vertically:(clipView==leftClip)];
  704.     
  705.     // Synchronize top ruler if visible and wasn't the given clipView
  706.     if (topViewVisible && (topClip != clipView))
  707.         [self synchronizeClipView:topClip withClipView:contentView
  708.             horizontally:YES vertically:NO];
  709.     
  710.     // Synchronize left ruler if visible and wasn't the given clipView
  711.     if(leftViewVisible && (leftClip != clipView))
  712.         [self synchronizeClipView:leftClip withClipView:contentView
  713.             horizontally:NO vertically:YES];
  714.  
  715.     // Scroll syncViews
  716.     for(i=0; i<[syncViews count]; i++) {
  717.         id view = [syncViews objectAt:i];
  718.         id subViews = [view subviews];
  719.         
  720.         // RawScroll the ClipView subViews, reflect scroll the contentView
  721.         for(j=0; j<[subViews count]; j++) {
  722.             id subView = [subViews objectAt:j];
  723.             if([subView isKindOf:[ClipView class]]) {
  724.                 [self synchronizeClipView:subView withClipView:contentView
  725.                     horizontally:YES vertically:YES];
  726.                 if([view docView] == [subView docView])
  727.                     [view reflectScroll:subView];
  728.             }
  729.         }
  730.     }
  731.     
  732.     // Scroll horizSyncViews
  733.     for(i=0; i<[horizSyncViews count]; i++) {
  734.         id view = [horizSyncViews objectAt:i];
  735.         id subViews = [view subviews];
  736.         
  737.         // horiz RawScroll the ClipView subViews, reflect scroll contentView
  738.         for(j=0; j<[subViews count]; j++) {
  739.             id subView = [subViews objectAt:j];
  740.             if([subView isKindOf:[ClipView class]]) {
  741.                 [self synchronizeClipView:subView withClipView:contentView
  742.                     horizontally:YES vertically:NO];
  743.                 if([view docView] == [subView docView])
  744.                     [view reflectScroll:subView];
  745.             }
  746.         }
  747.     }
  748.     
  749.     // Scroll vertSyncViews
  750.     for(i=0; i<[vertSyncViews count]; i++) {
  751.         id view = [vertSyncViews objectAt:i];
  752.         id subViews = [view subviews];
  753.         
  754.         // vert RawScroll the ClipView subViews, reflect scroll the contentView
  755.         for(j=0; j<[subViews count]; j++) {
  756.             id subView = [subViews objectAt:j];
  757.             if([subView isKindOf:[ClipView class]]) {
  758.                 [self synchronizeClipView:subView withClipView:contentView
  759.                     horizontally:NO vertically:YES];
  760.                 if([view docView] == [subView docView])
  761.                     [view reflectScroll:subView];
  762.             }
  763.         }
  764.     }
  765.  
  766.     return self;
  767. }
  768.  
  769.  
  770. /******************************************************************************
  771.     - synchronizeClipView:withClipView:horizontally:vertically:
  772.  
  773.     This method sets the first clipView so that it is viewing as much of the same rect that the withClipView is viewing as possible. It figures out this rect in the withClipView, corrects for coordinate system differences and flippedness and does a rawScroll in the given clipView.
  774.     The horizontally and vertically flags allow the synchronization to be constrained to a particular direction.
  775.     This method is called within scrollClip:to: to synchronize the various parts of the ScrollView (rulers, etc). You will probably never call it directly. Returns self.
  776. ******************************************************************************/
  777. - synchronizeClipView:newClip withClipView:oldClip
  778.     horizontally:(BOOL)horizSync vertically:(BOOL)vertSync
  779. {
  780.     // Get the offset of the oldClip's origin from its docView's origin 
  781.     float dx = [oldClip boundsX] - [[oldClip docView] frameX];
  782.     float dy = [oldClip boundsY] - [[oldClip docView] frameY];
  783.     NXPoint point;
  784.     
  785.     // Calc new X and Y by adding offset to newClips's docView's origin
  786.     point.x = [[newClip docView] frameX] + dx;
  787.     point.y = [[newClip docView] frameY] + dy;
  788.  
  789.     // If the flippedness is not the same, take the compliment of y
  790.     if([oldClip isFlipped] != [newClip isFlipped])
  791.         point.y = [[newClip docView] frameY] +
  792.         [[oldClip docView] frameHeight] - (dy + [newClip boundsHeight]);
  793.     
  794.     // Constrain scrolling from given flags
  795.     if(!horizSync) point.x = [newClip boundsX];
  796.     if(!vertSync)  point.y = [newClip boundsY];
  797.     
  798.     // Scroll to new boundsOrigin
  799.     [newClip rawScroll:&point];
  800.     return self;
  801. }
  802.             
  803.             /******************************************************************************
  804.     - reflectScroll
  805.  
  806.     This method is called to adjust the scrollers when there has been a change to the docView (it is called automatically durring autoscroll or when the docView is resized). We override it so that we can add or remove the page buttons if necessary. Returns self.
  807. ******************************************************************************/
  808. - reflectScroll:view
  809. {    
  810.     BOOL newUpDown, oldUpDown = [self needPageUpDownButtons];
  811.     BOOL newLeftRight,  oldLeftRight = [self needPageLeftRightButtons];
  812.  
  813.     [super reflectScroll:contentView];
  814.     
  815.     newUpDown = [self needPageUpDownButtons];
  816.     newLeftRight = [self needPageLeftRightButtons];
  817.     
  818.     // If scroller knobs appeared or disapeared, retile and display scrollers
  819.     if((newUpDown != oldUpDown) || (newLeftRight !=oldLeftRight)) {
  820.         [self tileScrollerViews];
  821.         [hScroller display]; [vScroller display];
  822.         [[self horizScrollerViews] makeObjectsPerform:@selector(display)];
  823.         [[self vertScrollerViews] makeObjectsPerform:@selector(display)];
  824.     }
  825.     return self;
  826. }
  827.  
  828. /******************************************************************************
  829.     - descendantFrameChanged:sender
  830.  
  831.     This method is called automatically whenever the document changes its frame size. We override it so that we can grow the dependent views respectively (topView, leftView, syncView, horizSyncView, vertSyncViews).
  832. ******************************************************************************/
  833. - descendantFrameChanged:sender
  834. {
  835.     int i;
  836.     
  837.     [super descendantFrameChanged:sender];
  838.  
  839.     // Size topView to docView's new height
  840.     if(topView) [[self topView] sizeTo:[[self docView] frameWidth] 
  841.             :[[self topView] frameHeight]];
  842.     
  843.     // Size leftView to docView's new height
  844.     if(leftView) [[self leftView] sizeTo:[[self leftView] frameWidth]
  845.         :[[self docView] frameHeight]]; 
  846.  
  847.     // Size syncViews to docViews new width and hieght
  848.     for(i=0; i<[syncViews count]; i++)
  849.         [[[syncViews objectAt:i] docView] sizeTo:[[self docView] frameWidth]
  850.             :[[self docView] frameHeight]];
  851.             
  852.     // Size horizSyncViews to docViews new width
  853.     for(i=0; i<[horizSyncViews count]; i++)
  854.         [[[horizSyncViews objectAt:i] docView] 
  855.             sizeTo:[[self docView] frameWidth] 
  856.             :[[[horizSyncViews objectAt:i] docView] frameHeight]];
  857.             
  858.     // Size vertSyncViews to docViews new hieght
  859.     for(i=0; i<[vertSyncViews count]; i++)
  860.         [[[vertSyncViews objectAt:i] docView] 
  861.             sizeTo:[[[vertSyncViews objectAt:i] docView] frameWidth] 
  862.             :[[self docView] frameHeight]];
  863.             
  864.  
  865.     return self;
  866. }
  867.  
  868. /******************************************************************************
  869.     - tile
  870.  
  871.     This method is where the real work of adding all of the subviews to the scrollview happens. It simply does a divide rect on the various parts and sets the origional parts to the diminished rect and the new parts to the new rect. Does not display. Returns self.
  872. ******************************************************************************/
  873. - tile
  874. {    
  875.     [super tile];
  876.  
  877.     if([self topViewVisible]) {
  878.         NXRect contentFrame = [contentView frame];
  879.         NXRect topClipFrame, cornerFrame;
  880.         
  881.         // Split contentView frame into topView part and contentView part
  882.         NXDivideRect(&contentFrame, &topClipFrame, rulerSize.height, NX_YMIN);
  883.         [contentView setFrame:&contentFrame];
  884.         
  885.         // If Both rulers exist, further divide topFrame for the corner
  886.         if([self leftViewVisible]) NXDivideRect(&topClipFrame, &cornerFrame, 
  887.             rulerSize.width, NX_XMIN);
  888.         [topClip setFrame:&topClipFrame];
  889.         
  890.         // Resize the topView to Min(docViewWidth, contentViewWidth)
  891.         [topView sizeTo:MAX([[self docView] frameWidth], 
  892.             [contentView boundsWidth]) :rulerSize.height]; 
  893.     }
  894.     
  895.     if([self leftViewVisible]) {
  896.         NXRect contentFrame = [contentView frame];
  897.         NXRect leftClipFrame;
  898.         
  899.         // Split contentView frame into leftView part and contentView part
  900.         NXDivideRect(&contentFrame, &leftClipFrame, rulerSize.width, NX_XMIN);
  901.         [contentView setFrame:&contentFrame];
  902.         [leftClip setFrame:&leftClipFrame];
  903.         
  904.         // Resize leftView to the Min(docView height, contentView height)
  905.         [leftView sizeTo:rulerSize.width 
  906.             :MAX([[self docView] frameHeight], [contentView boundsHeight])]; 
  907.     }
  908.     
  909.     // Retile the scroller views
  910.     [self tileScrollerViews];
  911.     
  912.     return self;
  913. }
  914.  
  915. /******************************************************************************
  916.     - tileScrollerViews
  917.  
  918.     This method tiles the views in the scrollers. Views are added to the scroller in the order they are encountered in their respective list. If they don't leave at least an inch for the scroller if added, they are not added.
  919.     I had to pull this code out of the tile method because the scrollers need to be retiled occasionally after a reflectScroll (to see if the page buttons go away). Since tile indirectly causes a reflectScroll, it cannot be called from within reflectScroll. Returns self.
  920. ******************************************************************************/
  921. - tileScrollerViews
  922. {
  923.     NXRect aRect, bRect;
  924.     int i;
  925.     
  926.     // Reset scrollers to their origional sizes
  927.     aRect = bounds;
  928.     if([self borderType] == NX_LINE) NXInsetRect(&aRect, 1, 1);
  929.     else if([self borderType] == NX_BEZEL) NXInsetRect(&aRect, 2, 2);
  930.  
  931.     if (_sFlags.vScrollerRequired) {
  932.         NXDivideRect(&aRect, &bRect, NX_SCROLLERWIDTH, NX_XMIN);
  933.         [vScroller setFrame:&bRect];
  934.         NXDivideRect(&aRect, &bRect, 1.0, 0);
  935.     }
  936.     if (_sFlags.hScrollerRequired) {
  937.         NXDivideRect(&aRect, &bRect, NX_SCROLLERWIDTH, NX_YMAX);
  938.         [hScroller setFrame:&bRect];
  939.     }
  940.  
  941.     // Set frames for each horizontal scroller view in order
  942.     for(i=0; i<[horizScrollerViews count]; i++) {
  943.         id hView = [horizScrollerViews objectAt:i];
  944.         NXRect horizRect = [hScroller frame];
  945.         NXRect viewRect  = [hView frame];
  946.  
  947.         // This metric says that if there would be less than an inch of 
  948.         //    scroller, then don't add this view.
  949.         if((NX_WIDTH(&horizRect) - NX_WIDTH(&viewRect)) > 72) {
  950.  
  951.             // Stick pageLeftRightButtons on the left (all others on the right)
  952.             if(hView == [self pageLeftRightButtons]) {
  953.             
  954.                 // If there is no need for page left & right buttons move them
  955.                 if(![self needPageLeftRightButtons])
  956.                     { NX_X(&viewRect)=-1000; NX_Y(&viewRect)=-1000; }
  957.                 
  958.                 // Otherwise, calculate their rect from the left
  959.                 else 
  960.                     NXDivideRect(&horizRect,&viewRect,
  961.                         NX_WIDTH(&viewRect),NX_XMIN);
  962.             }
  963.  
  964.             // Stick all normal horiz scroller views to the right
  965.             else
  966.                 NXDivideRect(&horizRect,&viewRect,NX_WIDTH(&viewRect),NX_XMAX);
  967.  
  968.             // Set the scroller frame
  969.             [hScroller setFrame:&horizRect];
  970.  
  971.             // Adjust the new scroller view frame to fit in scroller and set
  972.             NX_Y(&viewRect)++; NX_HEIGHT(&viewRect) = NX_HEIGHT(&viewRect) - 2;
  973.             [hView setFrame:&viewRect];
  974.         }
  975.         else [hView moveTo:-1000 :-1000];
  976.     }
  977.  
  978.     // Set frames for each vertical scroller view in order
  979.     for(i=0; i<[vertScrollerViews count]; i++) {
  980.         id vView = [vertScrollerViews objectAt:i];
  981.         NXRect vertRect = [vScroller frame];
  982.         NXRect viewRect = [vView frame];
  983.  
  984.         // This metric says that if there would be less than an inch of 
  985.         //    scroller, then don't add this view.
  986.         if((NX_HEIGHT(&vertRect) - NX_HEIGHT(&viewRect)) > 72) {
  987.  
  988.             // Stick pageLeftRightButtons on the left (all others on the right)
  989.             if(vView == [self pageUpDownButtons]) {
  990.             
  991.                 // If there is no need for page up & down buttons move them
  992.                 if(![self needPageUpDownButtons])
  993.                     { NX_X(&viewRect)=-1000; NX_Y(&viewRect)=-1000; }
  994.                     
  995.                 // Otherwise, calculate their rect from the bottom
  996.                 else NXDivideRect(&vertRect, &viewRect, 
  997.                     NX_HEIGHT(&viewRect), NX_YMAX);
  998.             }
  999.  
  1000.             // Stick all normal horiz scroller views at the top
  1001.             else 
  1002.                NXDivideRect(&vertRect,&viewRect, NX_HEIGHT(&viewRect),NX_YMIN);
  1003.  
  1004.             // Set the scroller frame
  1005.             [vScroller setFrame:&vertRect];
  1006.             
  1007.             // Adjust the new scroller view frame to fit in scroller and set
  1008.             NX_X(&viewRect)++; NX_WIDTH(&viewRect) = NX_WIDTH(&vertRect) - 2;
  1009.             [vView setFrame:&viewRect];
  1010.         }
  1011.         else [vView moveTo:-1000 :-1000];
  1012.     }
  1013.     return self;
  1014. }
  1015.  
  1016. // Overridden to handle "Size to Fit" zoom setting.
  1017. - sizeTo:(NXCoord)width :(NXCoord)height
  1018. {
  1019.     // Let super do its sizing
  1020.     [super sizeTo:width :height];
  1021.  
  1022.     // If zoomButton is visible and set to "Fit" (tag 6) do appropriate zoomTo
  1023.     if([self zoomButtonVisible] && 
  1024.         ([[[[self zoomButton] target] itemList] selectedTag] == 6)) {
  1025.         NXRect docViewBounds, contentFrame;
  1026.         [[self docView] getBounds:&docViewBounds];
  1027.         [contentView getFrame:&contentFrame];
  1028.  
  1029.         // Zoom so that docView's bounds == content's frame
  1030.         [self zoomTo:NX_WIDTH(&contentFrame)/(NX_WIDTH(&docViewBounds)+1)
  1031.             :NX_HEIGHT(&contentFrame)/(NX_HEIGHT(&docViewBounds)+1)];
  1032.     }
  1033.  
  1034.     return self;
  1035. }
  1036.  
  1037. // Overridden to synchronize all views after addition.
  1038. - setDocView:view
  1039. {
  1040.     NXPoint point = [contentView boundsOrigin];
  1041.     id oldDocView = [super setDocView:view];
  1042.     [self scrollClip:contentView to:&point];
  1043.     return oldDocView;    
  1044. }
  1045.  
  1046. - write:(NXTypedStream *)stream
  1047. {
  1048.     [super write:stream];
  1049.     
  1050.     NXWriteTypes(stream, "@@c@@c#{ff}@@@@@@c@c@c@@",
  1051.         &topView,
  1052.         &topClip,
  1053.         &topViewVisible,
  1054.         &leftView,
  1055.         &leftClip,
  1056.         &leftViewVisible,
  1057.         &rulerClass,
  1058.         &rulerSize,
  1059.         &syncViews,
  1060.         &horizSyncViews,
  1061.         &vertSyncViews,
  1062.         &horizScrollerViews,
  1063.         &vertScrollerViews,
  1064.         &pageUpDownButtons,
  1065.         &pageUpDownButtonsVisible,
  1066.         &pageLeftRightButtons,
  1067.         &pageLeftRightButtonsVisible,
  1068.         &zoomButton,
  1069.         &zoomButtonVisible,
  1070.         &zoomPanel,
  1071.         &zoomText);
  1072.     
  1073.     return self;
  1074. }
  1075.  
  1076. - read:(NXTypedStream *)stream
  1077. {
  1078.     [super read:stream];
  1079.     
  1080.     NXReadTypes(stream, "@@c@@c#{ff}@@@@@@c@c@c@@",
  1081.         &topView,
  1082.         &topClip,
  1083.         &topViewVisible,
  1084.         &leftView,
  1085.         &leftClip,
  1086.         &leftViewVisible,
  1087.         &rulerClass,
  1088.         &rulerSize,
  1089.         &syncViews,
  1090.         &horizSyncViews,
  1091.         &vertSyncViews,
  1092.         &horizScrollerViews,
  1093.         &vertScrollerViews,
  1094.         &pageUpDownButtons,
  1095.         &pageUpDownButtonsVisible,
  1096.         &pageLeftRightButtons,
  1097.         &pageLeftRightButtonsVisible,
  1098.         &zoomButton,
  1099.         &zoomButtonVisible,
  1100.         &zoomPanel,
  1101.         &zoomText);
  1102.     
  1103.     return self;
  1104. }
  1105.  
  1106. // Hack methods to allow each type of sync and scroller view to be added in IB.
  1107. - setSyncViews:object
  1108. { [self addSyncView:object at:[[self syncViews] count]];return self;}
  1109. - setHorizSyncViews:object
  1110. { [self addHorizSyncView:object at:[[self horizSyncViews] count]];return self;}
  1111. - setVertSyncViews:object
  1112. { [self addVertSyncView:object at:[[self vertSyncViews] count]]; return self; }
  1113. - setHorizScrollerViews:object
  1114. { [self addHorizScrollerView:object at:[[self horizScrollerViews] count]];
  1115.     return self; }
  1116. - setVertScrollerViews:object
  1117. { [self addVertScrollerView:object at:[[self vertScrollerViews] count]];
  1118.     return self; }
  1119.  
  1120. // Interface Builder support
  1121. - (const char *)getInspectorClassName { return "SVDInspector"; }
  1122.  
  1123. @end
  1124.  
  1125. @implementation View(Convenience)
  1126. - (NXCoord)frameX { return NX_X(&frame); }
  1127. - (NXCoord)frameY { return NX_Y(&frame); }
  1128. - (NXPoint)frameOrigin { return frame.origin; }
  1129. - (NXCoord)frameWidth { return NX_WIDTH(&frame); }
  1130. - (NXCoord)frameHeight { return NX_HEIGHT(&frame); }
  1131. - (NXSize)frameSize { return frame.size; }
  1132. - (NXRect)frame { return frame; }
  1133.  
  1134. - (NXCoord)boundsX { return NX_X(&bounds); }
  1135. - (NXCoord)boundsY { return NX_Y(&bounds); }
  1136. - (NXPoint)boundsOrigin { return bounds.origin; }
  1137. - (NXCoord)boundsWidth { return NX_WIDTH(&bounds); }
  1138. - (NXCoord)boundsHeight { return NX_HEIGHT(&bounds); }
  1139. - (NXSize)boundsSize { return bounds.size; }
  1140. - (NXRect)bounds { return bounds; }
  1141. @end
  1142.  
  1143. @implementation Scroller(PerCent)
  1144. - (float)perCent { return perCent; }
  1145. @end
  1146.